/*
//	CP2013 Traffic Simulator Assignment :: (MVC) Controller (Function Definitions)
//	Copyright (c) 2010, Adam Rehn
//
//	This file contains the function definitions for this component.
*/
#include "controller.h"

//Needed for time and random functions
#include <cstdlib>
#include <ctime>
#include <cmath>

//We are using std strings for generating SQL
#include <string>
using std::string;

//Needed for sorting
#include <algorithm>
using std::sort;

//Constructor and destructor
mvcController::mvcController(mvcModel* model)
{
	//Bind our pointer to the supplied model
	this->model = model;
	
	//Seed the random number generator
	srand(time(NULL));
	
	//Attempt to open the database
	if (sqlite3_open("results.sqlite", &db) != SQLITE_OK)
		throw "Could not open the database!";
	
	//If the results table does not exist, create it
	sqlite3_exec(db, "CREATE TABLE results (id INTEGER PRIMARY KEY, lanes_N INTEGER NOT NULL, lanes_S INTEGER NOT NULL, lanes_E INTEGER NOT NULL, lanes_W INTEGER NOT NULL, probability_N INTEGER NOT NULL, probability_S INTEGER NOT NULL, probability_E INTEGER NOT NULL, probability_W INTEGER NOT NULL, greenLength INTEGER, numCycles INTEGER, carsProcessed INTEGER);", NULL, NULL, NULL);
}

mvcController::~mvcController()
{
	//Close the database connection
	sqlite3_close(db);
}

//Use this to register a view that will receive the updated model each time a cycle is run
void mvcController::addView(mvcView* view)
{
	if (view != NULL)
	{
		//Check if the view is already registered in our list
		bool alreadyRegistered = false;
		for (int i = 0; i < views.size(); ++i)
		{
			if (views[i] == view)
				alreadyRegistered = true;
		}
		
		//Only add the view to our list if it is not already in it
		if (!alreadyRegistered)
		{
			//Add the view to our list
			views.push_back(view);
		}
		
		//Reset the view
		view->resetView(this->model);
	}
}

void mvcController::startSimulation()
{
	//No cars have passed through the intersection yet
	numCarsPassedThroughIntersection = 0;
	
	//No cycles have been run yet
	numCyclesRun = 0;
}

//This will run the specified number of cycles
void mvcController::runCycles(unsigned int num)
{
	//Perform the setup
	startSimulation();
	
	//Run the requested number of cycles
	for (int i = 1; i <= num; ++i)
		runCycle(i, num);
	
	//Commit the details to the database
	stopSimulation();
}

void mvcController::stopSimulation()
{
	//Don't bother commiting any details if no cycles were run
	if (numCyclesRun > 0)
	{
		//Prepare the SQL query to insert the generated statistics into the database
		string sql = "INSERT INTO results (lanes_N, lanes_S, lanes_E, lanes_W, probability_N, probability_S, probability_E, probability_W, greenLength, numCycles, carsProcessed) VALUES (";
		sql += toString(model->settings.lanes_N) + "," + toString(model->settings.lanes_S) + "," + toString(model->settings.lanes_E) + "," + toString(model->settings.lanes_W) + ",";
		sql += toString(model->settings.probability_N) + "," + toString(model->settings.probability_S) + "," + toString(model->settings.probability_E) + "," + toString(model->settings.probability_W) + ",";
		sql += toString(model->intSct.greenLength) + "," + toString(numCyclesRun) + "," + toString(numCarsPassedThroughIntersection) + ");";
		
		//Insert the generated statistics into the database
		sqlite3_exec(db, sql.c_str(), NULL, NULL, NULL);
	}
}

//This will run one cycle
bool mvcController::runCycle(unsigned int i, unsigned int t)
{
	/*
	//Keep track of the time we started processing
	//clock_t now = clock();
	
	//We are running a new cycle
	numCyclesRun++;
	
	//Loop through each of the vehicles and move them forward if possible
	for (int vehicleN = 0; vehicleN < model->vehicles.size(); ++vehicleN)
	{
		//Grab a reference to the vehicle
		vehicle currVehicle = model->vehicles[vehicleN];
		
		//This will hold the target tile's coordinates
		coordinate target = currVehicle.position;
		
		//For vehicles with speed greater than one, advance one tile at a time
		bool hasMoved = false;
		for (int spaceN = currVehicle.speed; spaceN > 0; --spaceN)
		{
			//If the vehicle is driving off of the map, remove it
			if (drivingOffTheEdgeOfTheWorld(currVehicle))
			{
				removeVehicle(vehicleN);
				vehicleN--;
			}
			else
			{
				//Determine the target tile based on the vehicle's direction
				switch (currVehicle.direction)
				{
					case DIRECTION_NORTH:
						target.y -= 1;
						break;
					
					case DIRECTION_SOUTH:
						target.y += 1;
						break;
					
					case DIRECTION_WEST:
						target.x -= 1;
						break;
					
					case DIRECTION_EAST:
						target.x += 1;
						break;
				}
				
				//If the target tile is clear of obstruction and the same direction as the vehicle, move there
				if (!isObstructed(target, currVehicle.direction))
				{
					//If we are entering the intersection, increment the counter
					if (isEnteringIntersection(currVehicle))
					{
						numCarsPassedThroughIntersection++;
						cout << "Vehicle entered the intersection." << endl;
					}
					
					//Update the vehicle in the array
					model->vehicles[vehicleN].position = target;
					hasMoved = true;
				}
			}
		}
		
		//If the vehicle hasn't moved, we can consider trying to overtake
		if (!hasMoved)
		{
			//Determine the diagonally forward and sideways tiles
			coordinate diagLeft, diagRight, sideLeft, sideRight;
			diagLeft = diagRight = sideLeft = sideRight = currVehicle.position;
			signed int w = 1, h = 1;
			
			//Determine the target tiles based on the vehicle's direction
			switch (currVehicle.direction)
			{
				case DIRECTION_NORTH:
					//Our Left is West
					diagLeft.x  -= 1;
					sideLeft.x  -= 1;
					
					//Our Right is East
					diagRight.x += 1;
					sideRight.x += 1;
					
					//Ahead is North
					diagLeft.y  -= 1;
					diagRight.y -= 1;
					
					//Behind is South
					h = currVehicle.length;
					break;
				
				case DIRECTION_SOUTH:
					//Our Left is East
					diagLeft.x  += 1;
					sideLeft.x  += 1;
					
					//Our Right is West
					diagRight.x -= 1;
					sideRight.x -= 1;
					
					//Ahead is South
					diagLeft.y  += 1;
					diagRight.y += 1;
					
					//Behind is North
					if (currVehicle.length > 1)
						h = 0 - (currVehicle.length - 1);
					else
						h = currVehicle.length;
					break;
				
				case DIRECTION_WEST:
					//Our Left is South
					diagLeft.y  += 1;
					sideLeft.y  += 1;
					
					//Our Right is North
					diagRight.y -= 1;
					sideRight.y -= 1;
					
					//Ahead is West
					diagLeft.x  -= 1;
					diagRight.x -= 1;
					
					//Behind is East
					w = currVehicle.length;
					break;
				
				case DIRECTION_EAST:
					//Our Left is North
					diagLeft.y  -= 1;
					sideLeft.y  -= 1;
					
					//Our Right is South
					diagRight.y += 1;
					sideRight.y += 1;
					
					//Ahead is East
					diagLeft.x  += 1;
					diagRight.x += 1;
					
					//Behind is West
					if (currVehicle.length > 1)
						w = 0 - (currVehicle.length - 1);
					else
						w = currVehicle.length;
					break;
			}
			
			//Use this to keep track of whether or not we are overtaking
			bool doOvertake = false;
			
			//If the left diagonal and sideways tiles are free, move left
			if (!isObstructed(diagLeft, currVehicle.direction) && !isObstructed(sideLeft, w, h, currVehicle.direction))
			{
				//Overtake left
				cout << "Overtake left." << endl;
				target = sideLeft;
				doOvertake = true;
			}
			if (!isObstructed(diagRight, currVehicle.direction) && !isObstructed(sideRight, w, h, currVehicle.direction))
			{
				//Overtake right
				cout << "Overtake right." << endl;
				target = sideRight;
				doOvertake = true;
			}
			
			//If we are overtaking, do so
			if (doOvertake)
			{
				//Move to the target tile and update the vehicle in the array
				model->vehicles[vehicleN].position = target;
				
				/*
				//Print the entire vehicles array of positions
				vector<coordinate> seen;
				bool collision = false;
				for (int d = 0; d < model->vehicles.size(); ++d)
				{
					cout << "Vehicle " << d << " at (" << model->vehicles[d].position.x << "," << model->vehicles[d].position.y << ")" << endl;
					for (int hg = 0; hg < seen.size(); ++hg)
					{
						if (seen[hg] == model->vehicles[d].position)
							collision = true;
					}
					if (!collision)
						seen.push_back(model->vehicles[d].position);
				}
				if (collision)
					cout << "A collision has occured!" << endl;
				cout << endl;
				*//*
			}
		}
	}
	
	//Keep track of the number of blocked spawn points
	unsigned char blockedSpawnPoints = 0;
	
	//Loop through each of the lanes and spawn cars based on probability
	for (int roadN = 0; roadN < 4; ++roadN)
	{
		for (int laneN = 0; laneN < model->roads[roadN]->size(); ++laneN)
		{
			//Grab a reference to the current lane's spawn point, probability, and direction
			coordinate   spawnPoint  = model->roads[roadN]->at(laneN).spawnPoint;
			unsigned int probability = model->roads[roadN]->at(laneN).probability;
			direction_t  direction   = model->roads[roadN]->at(laneN).direction;
			
			//Generate a random number up to one hundred (inclusive)
			unsigned int randNum = rand() % 101;
			
			//If the random number is lower than the probability, spawn a car
			if (randNum < probability)
			{
				//Check that the spawn point is not obstructed
				if (isObstructed(spawnPoint))
				{
					//Increase the count of blocked spawn points
					blockedSpawnPoints++;
				}
				else
				{
					//Create a new car at the spawn point
					vehicle newCar;
					newCar.position.x = spawnPoint.x;
					newCar.position.y = spawnPoint.y;
					newCar.length = 1;
					//newCar.speed = 1;
					newCar.direction = direction;
					
					//Each car has a random colour
					newCar.colour = rand() % 256;
					
					//Temporary: random speed between 1 and 3
					newCar.speed = 1 + (rand() % 2);
					
					//Add the new car
					model->vehicles.push_back(newCar);
				}
			}
		}
	}
	
	//Temporary: set the congestion level threshold to 4 blocked spawn points
	if (blockedSpawnPoints == 4)
	{
		//This block being reached indicates congestion, and thus
		//a failure of the designed traffic system. The GUI will
		//report this error to the user.
		return false;
	}
	
	//Sort the vehicles array in descending order of speed
	sort(model->vehicles.begin(), model->vehicles.end(), isFaster);
	
	//If there is a yellow light present, we need to check that the intersection is clear
	if (model->intSct.yellowOn())
	{
		if (!isObstructed(model->intSct.anchor, model->intSct.width, model->intSct.height))
		{
			//The intersection is clear, move to the next state in the cycle
			model->intSct.cycleState();
			updateIntersectionTiles();
		}
	}
	else
	{
		//No yellow present, move to the next state in the cycle
		model->intSct.cycleState();
		updateIntersectionTiles();
	}
	
	//Push the updated model to all associated views
	for (int v = 0; v < views.size(); ++v)
		views[v]->updateView(this->model/*, now, i, t*//*);
	
	//Success!
	*/return true;
}

//This will check if a specific tile is obstructed
bool mvcController::isObstructed(coordinate t, direction_t dir)
{
	/*
	//First, if a direction has been supplied, the target tile must support it
	if (dir != DIRECTION_UNDEFINED && !(model->map->at(t.x, t.y) & dir))
		return true;
	
	//Loop through each of the vehicles and check their size and position
	for (int i = 0; i < model->vehicles.size(); ++i)
	{
		//Grab a reference to the vehicle
		vehicle currVehicle = model->vehicles[i];
		
		//If the vehicle is only one tile long, simply check if its position matches
		if (currVehicle.length == 1)
		{
			if (currVehicle.position == t)
				return true;
		}
		else
		{
			//Determine which tiles the vehicle obstructs
			for (int j = 0; j < currVehicle.length; ++j)
			{
				//Start with the vehicle location
				coordinate obsTile = currVehicle.position;
				
				//Based on the direction of the vehicle, proceed outward
				switch (currVehicle.direction)
				{
					case DIRECTION_NORTH:
						obsTile.y += j;
						break;
					
					case DIRECTION_SOUTH:
						obsTile.y -= j;
						break;
					
					case DIRECTION_WEST:
						obsTile.x += j;
						break;
					
					case DIRECTION_EAST:
						obsTile.x -= j;
						break;
				}
				
				//If the obstructed tile is the target, return true
				if (obsTile == t)
					return true;
			}
		}
	}
	*/
	//If we get to this line, the tile is unobstructed
	return false;
}

//This will check if a range of tiles are obstructed
bool mvcController::isObstructed(coordinate base, signed int w, signed int h, direction_t dir)
{
	/*
	//For negative values, move the rectangle
	if (w < 0)
	{
		//Flip the width to be positive
		w = 0 - w;
		
		//Move the x coordinate
		base.x -= w;
	}
	if (h < 0)
	{
		//Flip the height to be positive
		h = 0 - h;
		
		//Move the y coordinate
		base.y -= h;
	}
	
	//Loop through the rectange
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			coordinate c = base;
			c.x += x;
			c.y += y;
			if (isObstructed(c, dir))
				return true;
		}
	}
	*/
	//If we get to this line, none of the tiles are obstructed
	return false;
}

//This will check if a specific vehicle is trying to drive off of the map
bool mvcController::drivingOffTheEdgeOfTheWorld(vehicle& v)
{
	/*
	//Check the direction the vehicle is facing and if it is on the edge of the map
	switch (v.direction)
	{
		case DIRECTION_NORTH:
			if (v.position.y == 0) return true;
			break;
		
		case DIRECTION_WEST:
			if (v.position.x == 0) return true;
			break;
		
		case DIRECTION_EAST:
			if (v.position.x == model->map->getWidth() - 1) return true;
			break;
		
		case DIRECTION_SOUTH:
			if (v.position.y == model->map->getHeight() - 1) return true;
			break;
	}
	*/
	//If we get to this line, the vehicle is not leaving the map
	return false;
}

//This will check if a specific vehicle is entering the intersection
bool mvcController::isEnteringIntersection(vehicle& v)
{
	/*
	//Loop through the lanes to determine if the vehicle is on the intersection entry point
	for (int roadN = 0; roadN < 4; ++roadN)
	{
		for (int laneN = 0; laneN < model->roads[roadN]->size(); ++laneN)
		{
			//Grab a reference to the current lane's intersection entry point
			coordinate entryPoint = model->roads[roadN]->at(laneN).intersectionEntryPoint;
			
			//Only bother checking anything if the lane actually has an intersection entry point
			if (entryPoint != coordinate(0,0))
			{
				//To be entering the intersection, a vehicle must be on the entry point facing the same way as the lane
				if (v.position == entryPoint && (model->roads[roadN]->at(laneN).direction & v.direction))
					return true;
			}
		}
	}
	*/
	//The vehicle is not entering the intersection
	return false;
}

//This will remove a vehicle from memory
void mvcController::removeVehicle(unsigned int i)
{
	if (i < model->vehicles.size())
	{
		model->vehicles.erase(model->vehicles.begin() + i);
		cout << "Vehicle deleted." << endl;
	}
}

//This will alter the directions of all of the tiles in the intersection to match the state of the traffic lights
void mvcController::updateIntersectionTiles()
{
	/*
	//If no yellow is present, a green must be present, so we can fill all of the tiles
	if (!model->intSct.yellowOn())
	{
		//Create a temporary variable to hold the new tile direction
		direction_t dir;
		
		//Determine which direction the green is for
		if (model->intSct.lightNS.state == GREEN)
			dir = DIRECTION_NORTH|DIRECTION_SOUTH;
		else
			dir = DIRECTION_WEST|DIRECTION_EAST;
		
		//Loop through all of the tiles and update their directions
		for (int y = 0; y < model->intSct.height; ++y)
		{
			for (int x = 0; x < model->intSct.width; ++x)
			{
				coordinate t = model->intSct.anchor;
				t.x += x;
				t.y += y;
				
				//Update the tile
				model->map->setTile(t.x, t.y, dir);
			}
		}
	}
	else
	{
		//A yellow is present, so we need to set two particular edges of the intersection to be block incoming traffic
		coordinate base = model->intSct.anchor;
		
		//Determine which direction the yellow is for
		if (model->intSct.lightNS.state == YELLOW)
		{
			//The yellow is for North/South, so we need to determine the height
			
			//If the intersection is only 1 tile high, fill all of the tiles with DIRECTION_UNDEFINED
			if (model->intSct.height == 1)
				model->map->setTileRange(base, model->intSct.width, model->intSct.height, DIRECTION_UNDEFINED);
			else
			{
				//Otherwise, we set the top and bottom edges to block
				model->map->setTileRange(base, model->intSct.width, 1, DIRECTION_NORTH);
				base.y += (model->intSct.height - 1);
				model->map->setTileRange(base, model->intSct.width, 1, DIRECTION_SOUTH);
			}
		}
		else
		{
			//The yellow is for West/East, so we need to determine the width
			
			//If the intersection is only 1 tile wide, fill all of the tiles with DIRECTION_UNDEFINED
			if (model->intSct.width == 1)
				model->map->setTileRange(base, model->intSct.width, model->intSct.height, DIRECTION_UNDEFINED);
			else
			{
				//Otherwise, we set the left and right edges to block
				model->map->setTileRange(base, 1, model->intSct.height, DIRECTION_WEST);
				base.x += (model->intSct.width - 1);
				model->map->setTileRange(base, 1, model->intSct.height, DIRECTION_EAST);
			}
		}
		
	}
	*/
}
